#include "fiber.h"
#include "scheduler.h"
#include "util.h"
#include "log.h"
#include "global.h"

#include <cstdint>
#include <exception>
#include <iostream>
#include <atomic>
#include <memory>
#include <ucontext.h>
#include <thread>

class MallocStackAllocator
{
public:
    static void* Alloc(size_t size)
    {
        return malloc(size);
    }

    static void Dealloc(void* vp, size_t size)
    {
        return free(vp);
    }
};
using StackAllocator = MallocStackAllocator;

static std::atomic<uint64_t> s_fiber_id {0};    //协程id
static std::atomic<uint64_t> s_fiber_count {0}; //协程数量

static thread_local Fiber* t_fiber = nullptr; //线程的当前协程
static thread_local Fiber::ptr t_threadFiber = nullptr; //线程的主协程MainFiber=

//默认构造函数用于构造线程的主协程
//私有构造会被static函数调用
Fiber::Fiber()
{
    m_state = EXEC;   //初始化协程为运行状态
    SetThis(this);  //设置线程的当前协程为主协程

    //获取当前线程的上下文
    if(getcontext(&m_ctx))
    {
        //返回非0调用失败，终止程序输出栈信息
        ASSERT2(false, "Fiber::Fiber() getcontext");
    }

    ++s_fiber_count;
    log_info("创建主协程 thread id {}", Scheduler::GetThreadId(std::this_thread::get_id()));
}

//有参构造创建子协程
Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool is_schedul, const std::string info)
:m_id(++s_fiber_id)
,m_cb(cb)
{
    ++s_fiber_count; //增加协程数量

    m_stackSize = stacksize ? stacksize : g_fiber_stack_size;

    m_stack = StackAllocator::Alloc(m_stackSize);

    //获取线程上下文信息
    if(getcontext(&m_ctx))
    {
        //返回非0调用失败，终止程序输出栈信息
        ASSERT2(false, "Fiber::Fiber(...) getcontext");
    }
    m_ctx.uc_link = nullptr;              //此协程函数执行完成后退出
    m_ctx.uc_stack.ss_sp = m_stack;       //协程函数执行时 栈的起始地址
    m_ctx.uc_stack.ss_size = m_stackSize; //栈大小

    //创建协程
    if(is_schedul)
    {
        makecontext(&m_ctx, &Fiber::SchedulMainFunc, 0);
        log_info("创建{}协程 schedul - fiber id = {}\tthread id = {}",
            info, m_id, Scheduler::GetThreadId(std::this_thread::get_id()));
    }
    else 
    {
        makecontext(&m_ctx, &Fiber::MainFunc, 0);
        log_info("创建{}协程  fiber id = {}\tthread id = {}",
            info, m_id, Scheduler::GetThreadId(std::this_thread::get_id()));
    }
}

Fiber::~Fiber()
{
    --s_fiber_count;
    if(m_stack) //如果开辟过栈空间（走有参构造）
    {
        ASSERT(m_state == TERM
                     || m_state == EXCEPT
                     || m_state == INIT);

        //断言协程状态 不是结束、异常、初始化 就释放空间
        StackAllocator::Dealloc(m_stack, m_stackSize);

        log_info("任务协程结束  fiber id = {}\t协程数量 = {}\tthread id = {}",
            m_id, TotalFibers(), Scheduler::GetThreadId(std::this_thread::get_id()));
    }
    else //释放线程的主协程 （走默认构造）
    {
        ASSERT(!m_cb);  //断言回调函数为空
        ASSERT(m_state == EXEC); //断言协程状态为执行中

        Fiber* cur = t_fiber;
        if(cur == this)
        {
            SetThis(nullptr); //将线程的主协程置空
        }

        log_info("线程主协程结束  fiber id = {}\t协程数量 = {}\tthread id = {}",
            m_id, TotalFibers(), Scheduler::GetThreadId(std::this_thread::get_id()));
    }
}

//重置协程执行函数，并设置状态
//getState() 为 INIT, TERM, EXCEPT 
void Fiber::resetFunc(std::function<void()> cb)
{
    ASSERT(m_stack); //断言栈不为空
    //如果当前协程不为 结束 异常 初始化 就说明协程正在执行
    ASSERT(m_state == TERM || m_state == EXCEPT || m_state == INIT);

    m_cb = cb;

    //重新获取线程上下文信息
    if(getcontext(&m_ctx))
    {
        ASSERT2(false, "Fiber::reset getcontext");
    }
    m_ctx.uc_link = nullptr;              //此协程函数执行完成后退出
    m_ctx.uc_stack.ss_sp = m_stack;       //协程函数执行时 栈的起始地址
    m_ctx.uc_stack.ss_size = m_stackSize; //栈大小

    //创建协程
    makecontext(&m_ctx, &Fiber::SchedulMainFunc, 0);
    m_state = INIT;
}

//将当前协程切换到运行状态
//getState() != EXEC  ->  getState() = EXEC
void Fiber::swapIn()
{
    SetThis(this); //设置当前协程为自己
    ASSERT(m_state != EXEC); //断言当前协程不为执行状态
    m_state = EXEC;

    //保存调度协程执行上下文，并切换到指定的协程上下文
    if(swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx))   //将当前协程切换到后台
    {
        ASSERT2(false, "swapIn swapcontext");
    }
}

//将当前协程切换到后台 但是没有设置当前协程的状态，设置状态由 YieldToReady 和 YieldToHold 执行
void Fiber::swapOut()
{
    SetThis(Scheduler::GetMainFiber());

    //保存当前协程执行上下文，并切换到主协程上下文
    //保存当前协程执行上下文，并切换到调度协程上下文
    if(swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx))   //将当前协程切换到后台
    {
        ASSERT2(false, "swapOut swapcontext");
    }
}



//将当前线程切换到执行状态
void Fiber::call()
{
    SetThis(this); //设置当前协程为自己
    ASSERT(m_state != EXEC); //断言当前协程不为执行状态
    m_state = EXEC;

    //保存主协程执行上下文，并切换到指定的协程上下文
    if(swapcontext(&t_threadFiber->m_ctx, &m_ctx))   //将当前协程切换到后台
    {
        ASSERT2(false, "call swapcontext");
    }
}

//将当前线程切换到后台, 返回到线程的主协程
void Fiber::back()
{
    SetThis(t_threadFiber.get());

    //保存当前协程执行上下文，并切换到主协程上下文
    if(swapcontext(&m_ctx, &t_threadFiber->m_ctx))   //将当前协程切换到后台
    {
        ASSERT2(false, "swapOut swapcontext");
    }
}


//设置当前线程的运行协程
void Fiber::SetThis(Fiber* f)
{
    t_fiber = f;
}


//返回当前所在协程 - 如果当前线程还没有协程，就创建线程的主协程
Fiber::ptr Fiber::GetThis()
{
    if(t_fiber)
    {
        return t_fiber->shared_from_this();
    }
    
    //如果当前线程还没有创建过协程 就创建线程的主协程
    Fiber::ptr main_fiber(new Fiber);
    ASSERT(t_fiber == main_fiber.get());
    t_threadFiber = main_fiber; //设置线程的全局变量

    return t_fiber->shared_from_this();
}


//将当前协程切换到后台,并设置为可执行状态
void Fiber::YieldToReady()
{
    Fiber::ptr cur = GetThis();
    ASSERT(cur->m_state == EXEC);
    cur->m_state = READY;
    cur->swapOut();
}


//将当前协程切换到后台,并设置为暂停状态
void Fiber::YieldToHold()
{
    Fiber::ptr cur = GetThis();
    ASSERT(cur->m_state == EXEC);
    cur->m_state = HOLD;
    cur->swapOut();
}

void Fiber::BackToReady()
{
    Fiber::ptr cur = GetThis();
    ASSERT(cur->m_state == EXEC);
    cur->m_state = READY;
    cur->back();
}
void Fiber::BackToHold()
{
    Fiber::ptr cur = GetThis();
    ASSERT(cur->m_state == EXEC);
    cur->m_state = HOLD;
    cur->back();
}


//返回当前协程的总数量
uint64_t Fiber::TotalFibers()
{
    return s_fiber_count;
}


//协程执行函数  执行完成返回到调度协程
void Fiber::SchedulMainFunc()
{
    //当前协程不应该为空
    Fiber::ptr cur = GetThis();
    ASSERT(cur);

    try 
    {
        cur->m_cb();         //执行协程回调
        cur->m_cb = nullptr; //清空协程回调
        cur->m_state = TERM;
    } 
    catch (const std::exception& ex) 
    {
        cur->m_state = EXCEPT;
        std::cerr << "Fiber Except:" << ex.what()
            << " fiber_id=" << cur->getId()
            << std::endl
            << BacktraceToString();
    }
    catch(...)
    {
        cur->m_state = EXCEPT;
        std::cerr << "Fiber Except:"
            << " fiber_id=" << cur->getId()
            << std::endl
            << BacktraceToString();
    }

    //协程函数执行完毕时，释放智能指针，并将协程切换回主协程
    auto raw_ptr = cur.get();
    cur.reset();
    raw_ptr->swapOut();

    //不应该执行到这里
    ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}


//协程执行函数  执行完成返回到线程主协程
void Fiber::MainFunc()
{
    //当前协程不应该为空
    Fiber::ptr cur = GetThis();
    ASSERT(cur);

    try 
    {
        cur->m_cb();         //执行协程回调
        cur->m_cb = nullptr; //清空协程回调
        cur->m_state = TERM;
    } 
    catch (const std::exception& ex) 
    {
        cur->m_state = EXCEPT;
        std::cerr << "Fiber Except:" << ex.what()
            << " fiber_id=" << cur->getId()
            << std::endl
            << BacktraceToString();
    }
    catch(...)
    {
        cur->m_state = EXCEPT;
        std::cerr << "Fiber Except:"
            << " fiber_id=" << cur->getId()
            << std::endl
            << BacktraceToString();
    }

    //协程函数执行完毕时，释放智能指针，并将协程切换回主协程
    auto raw_ptr = cur.get();
    cur.reset();
    raw_ptr->back();

    //不应该执行到这里
    ASSERT2(false, "never reach fiber_id=" + std::to_string(raw_ptr->getId()));
}


//获取当前协程的id
uint64_t Fiber::GetFiberId()
{
    if(t_fiber) //如果当前线程创建了协程
    {
        return t_fiber->getId();
    }
    return 0;
}
